#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include "utilidades.h"
#include "termino.h"
#include "lista.h"
#include "vector_dinamico.h"


const int TAM_INI_CADENA=10;
const char* STOP_WORDS_ROUTE = "../stop_words.csv";
/* 
 *
 *	Lee una linea de un archivo de texto. Devuelve un string con la linea
 *	El usuario debe encargarse de liberar la memoria ocupada por el string devuelto.
 *	Si se esta al final del archivo, devuelve un caracter de fin de linea (\n) (creo).
 *
 */
char *leer_linea(FILE* archivo){
    int tam = TAM_INI_CADENA,i=0;
    char *linea = (char*)malloc(sizeof(char)*tam);
    char letra;
    do {
        letra = fgetc(archivo);
        linea[i]=letra;
        if (tam==i+1){
            tam+=10;
            char *aux=(char*) realloc(linea,sizeof(char)*tam);
            if (!aux) {
                linea[i]='\0';
                return linea;
            } else {
                linea=aux;
            }
        }
        i++;
    } while (letra!='\n' && letra!=EOF);
    linea[i-1]='\0';
    return linea;
}

char *leer_hasta(FILE* archivo, char separador){
    int tam = TAM_INI_CADENA,i=0;
    char *linea = (char*)malloc(sizeof(char)*tam);
    char letra;
    do {
        letra = fgetc(archivo);
        linea[i]=letra;
        if (tam==i+1){
            tam+=10;
            char *aux=(char*) realloc(linea,sizeof(char)*tam);
            if (!aux) {
                linea[i]='\0';
                return linea;
            } else {
                linea=aux;
            }
        }
        i++;
    } while (letra!= separador && letra!=EOF);
    linea[i-1]='\0';
    return linea;
}



void string_a_minusculas(char* string){
	for (int i = 0; string[i] != '\0'; i++)
		string[i] = (char)tolower(string[i]);
  
}
 
 


void correr_array_a_derecha_una_posicion(vector_t* vector, int largo){

	char* temp = (char*)vector_obtener(vector,0); //Grab the first number from the array

	for(int i = 0; i < largo - 1; i++){
		char* siguiente = (char*)vector_obtener(vector,i+1);
		vector_guardar(vector,i,siguiente); //Makes all the array indexes equal the string after them
	}
	//Marco el ultimo como libre
	vector_guardar(vector,largo-1,NULL);
	free(temp);
}



/* 
	Pasa todas las combinaciones como terminos a la lista	
*/

bool pasar_terminos_de_1_a_N_a_la_lista(lista_t* lista,vector_t* vector,int N){
	char* copia=NULL;
	for (int i=0; i<N; i++){
		lista_t* lista_de_componentes = lista_crear();
		
		for (int j=N-1;j>=N-1-i;j--){
			char* palabra_original = (char*)vector_obtener(vector,j);	
			copia = malloc(sizeof(char)*(strlen(palabra_original)+1));
			strcpy(copia,palabra_original);
			lista_insertar_primero(lista_de_componentes,copia);
		}
		
		termino_t* termino = termino_crear(lista_de_componentes);
		if (!lista_insertar_ultimo(lista,termino)) return false;
	
	}	
	
	//Agrego un elemento al vector y saco el primero de los que estaba
	correr_array_a_derecha_una_posicion(vector, N);
	
	return true;
}

//Funcion recursiva que hizo historia.
bool pasar_terminos_de_1_a_N_a_la_lista_recursiva(lista_t* lista,vector_t* vector,int N){
	char* copia=NULL;
	if (N==0) return true;
	for (int i=0; i<N; i++){
		lista_t* lista_de_componentes = lista_crear();
		
		for (int j=N-1;j>=N-1-i;j--){
			char* palabra_original = (char*)vector_obtener(vector,j);	
			copia = malloc(sizeof(char)*(strlen(palabra_original)+1));
			strcpy(copia,palabra_original);
			lista_insertar_primero(lista_de_componentes,copia);
		}
		
		termino_t* termino = termino_crear(lista_de_componentes);
		if (!lista_insertar_primero(lista,termino)) return false;
	
	}	
	pasar_terminos_de_1_a_N_a_la_lista_recursiva(lista,vector,N-1);
	return true;
}

/* 
 * Procesa el ultimo termino de todos; toma en cuenta 3 posibilidades:
 * linea vacia
 * linea con menos palabras que las de un N-grama maximo
 * linea con >= palabras q un n-grama maximo
 */
lista_t* procesar_ultimo_termino(lista_t* lista_terminos, vector_t* vector_buffer, int cont, int nro_de_palabras_por_termino,bool linea_vacia){
	if (linea_vacia){
			vector_destruir(vector_buffer,NULL);
			return lista_terminos;
	}
		//Si la linea tenia menos palabras que el minimo:
	if (cont < nro_de_palabras_por_termino){
			pasar_terminos_de_1_a_N_a_la_lista_recursiva(lista_terminos,vector_buffer,cont);
			vector_destruir(vector_buffer,free);
			return lista_terminos;
	}
	pasar_terminos_de_1_a_N_a_la_lista(lista_terminos,vector_buffer,nro_de_palabras_por_termino);
	vector_destruir(vector_buffer,free);
	return lista_terminos;
	
}

void pasar_lista_a_avl(lista_t* lista,abb_avl_t* avl){
	
	lista_iter_t* iter = lista_iter_crear(lista);
	
	while(!lista_iter_al_final(iter)){
		termino_t* actual = (termino_t*) lista_iter_ver_actual(iter);
		avl_guardar(avl,actual->valor,NULL);
		
		lista_iter_avanzar(iter);
	}
	
	lista_iter_destruir(iter);
}


/* Dada la cadena leida, extrae una lista de stop words */
lista_t* extraer_stop_words(char* cadena, int nro_de_palabras_por_termino){
	
	if( !cadena ) return NULL;
	
	// Inicializo variables que voy a necesitar
	int aux_tamanio;
	char* nuevo_termino = NULL;
	char* aux_string = NULL;
	
	lista_t* lista_terminos = lista_crear();
	int cont = 0;
	
	//Esto se ocupa del caso particular de lineas completamente llenas de tokens o vacias
	bool linea_vacia=true;
	
	vector_t* vector_buffer = vector_crear(nro_de_palabras_por_termino);
	
	//Inicializo el vector
	for(int i =0;i<nro_de_palabras_por_termino;i++)
		vector_guardar(vector_buffer,i,NULL);
	
	while( true ){
		
		// Separo las palabras con respecto a los espacios, las comas y los guiones.
		
		aux_string = strtok(cadena,"\r ,—.?¡!\"\'()[]{}$#;:\n\r\n<>*-/\\\t_=´+~`¨");

		// Si devuelve NULL es porque termino la linea.
		if( !aux_string ) return procesar_ultimo_termino(lista_terminos,vector_buffer,cont,nro_de_palabras_por_termino,linea_vacia);
		
		linea_vacia = false;
		if (cadena) cadena = NULL;
		
		aux_tamanio = strlen(aux_string);			
		
		// El nuevo termino con su tamanio correspondiente.
		
		nuevo_termino = (char*) malloc( (aux_tamanio+1) * sizeof(char) );
		strcpy(nuevo_termino,aux_string);
		strcat(nuevo_termino,"\0");
		
		string_a_minusculas(nuevo_termino);
		//Los primeros N no se procesan
		if (cont < nro_de_palabras_por_termino){
			vector_guardar(vector_buffer,cont,nuevo_termino);
			cont++;
			//Si justo llegue, proceso las anteriores
			if (cont == nro_de_palabras_por_termino)
				pasar_terminos_de_1_a_N_a_la_lista_recursiva(lista_terminos,vector_buffer,nro_de_palabras_por_termino-1);
			continue;
		}
		cont++;
		if (!pasar_terminos_de_1_a_N_a_la_lista(lista_terminos,vector_buffer,nro_de_palabras_por_termino))
			return NULL;	
			
		vector_guardar(vector_buffer,nro_de_palabras_por_termino-1,nuevo_termino);
					
	}
	
	vector_destruir(vector_buffer,free);
	return lista_terminos;
	
}

void wrapper_dest_termino (void* termino){
	termino= (termino_t*)termino;
	termino_destruir(termino);
}



abb_avl_t* cargar_stop_words(){
	abb_avl_t* avl_stop_words = avl_crear(strcmp,NULL);
	FILE* archivo = fopen(STOP_WORDS_ROUTE,"r");
	if(!archivo) return NULL;
	while(!feof(archivo)){
		char* linea = leer_linea(archivo);
		//Esto es un bug del leer linea, lo mejor seria solucionarlo ahi
		if (strcmp(linea,"")==0 || strcmp(linea,"\n")==0 || strcmp(linea,"\r")==0  || strcmp(linea,"\r\n")==0){
			cadena_destruir(linea);
			continue;	
		}
	lista_t* lista_stop_words = extraer_stop_words(linea,1);
	pasar_lista_a_avl(lista_stop_words,avl_stop_words);
	lista_destruir(lista_stop_words,wrapper_dest_termino);
	free(linea);
	}
	fclose(archivo);
	return avl_stop_words;
}














/* Separa la cadena recibida a partir de los caracteres indicados.
 * Devuelve una lista de terminos, con un NULL en la ultima posicion
 * de la lista, para indicar el fin de la lista de terminos. No se
 * encarga de liberar la memoria de la cadena recibida. */
lista_t* separar_cadena_en_terminos(char* cadena, int nro_de_palabras_por_termino,abb_avl_t* avl_stop_words){
	
	if( !cadena ) return NULL;
	
	// Inicializo variables que voy a necesitar
	int aux_tamanio;
	char* nuevo_termino = NULL;
	char* aux_string = NULL;
	
	lista_t* lista_terminos = lista_crear();
	int cont = 0;
	
	//Esto se ocupa del caso particular de lineas completamente llenas de tokens o vacias
	bool linea_vacia=true;
	
	vector_t* vector_buffer = vector_crear(nro_de_palabras_por_termino);
	
	//Inicializo el vector
	for(int i =0;i<nro_de_palabras_por_termino;i++)
		vector_guardar(vector_buffer,i,NULL);
	
	while( true ){
		
		// Separo las palabras con respecto a los espacios, las comas y los guiones.
		
		aux_string = strtok(cadena,"\r ,—.?¡!\"\'()[]{}$#;:\n\r\n<>*-/\\\t_=´+~`¨");

		// Si devuelve NULL es porque termino la linea.
		if( !aux_string ) return procesar_ultimo_termino(lista_terminos,vector_buffer,cont,nro_de_palabras_por_termino,linea_vacia);
		
		if (cadena) cadena = NULL;
		
		//Filtro stop words
		string_a_minusculas(aux_string);
		if (avl_stop_words)
			if(avl_pertenece(avl_stop_words,aux_string))continue;
	
		linea_vacia = false;
		
		
		aux_tamanio = strlen(aux_string);			
		
		// El nuevo termino con su tamanio correspondiente.
		
		nuevo_termino = (char*) malloc( (aux_tamanio+1) * sizeof(char) );
		strcpy(nuevo_termino,aux_string);
		strcat(nuevo_termino,"\0");
		
		string_a_minusculas(nuevo_termino);
		//Los primeros N no se procesan
		if (cont < nro_de_palabras_por_termino){
			vector_guardar(vector_buffer,cont,nuevo_termino);
			cont++;
			//Si justo llegue, proceso las anteriores
			if (cont == nro_de_palabras_por_termino)
				pasar_terminos_de_1_a_N_a_la_lista_recursiva(lista_terminos,vector_buffer,nro_de_palabras_por_termino-1);
			continue;
		}
		cont++;
		if (!pasar_terminos_de_1_a_N_a_la_lista(lista_terminos,vector_buffer,nro_de_palabras_por_termino))
			return NULL;	
			
		vector_guardar(vector_buffer,nro_de_palabras_por_termino-1,nuevo_termino);
					
	}
	
	vector_destruir(vector_buffer,free);
	return lista_terminos;
	
}

void cadena_destruir(char* cadena){
	
	if (cadena) free(cadena);
}

/*
 *
 * Lee N bytes (chars) del archivo. Luego, sigue leyendo hasta encontrar on EOL.
 * 
 */

char* leer_bloque_string_de_archivo(FILE* archivo, int N){
	
	char* buffer = malloc(sizeof(char)* (N + 1) ); // +1 lugar para el \0
	
	//Inicializo el buffer xq sino valgrind tira errores =|
	memset(buffer,0,N+1); 
	
	//Leo N chars y los pongo en buffer
	int cantidad = fread(buffer,sizeof(char),N,archivo);
	buffer[cantidad]='\0';
	
	//Leo a partir de acá hasta un /n

	char* sobrante = leer_linea(archivo);
	
	char* string_leido = malloc((strlen(buffer)+strlen(sobrante)+3)*sizeof(char));

	strcpy(string_leido,buffer);
	string_leido[cantidad]='\0';
	if (sobrante){
		strcat(string_leido,sobrante);
		strcat(string_leido,"\0");
		free(sobrante);
	}
	free(buffer);
	return string_leido;
}




